#include <fstream>
#include <iostream>

#include "EEExpMain.h"
#include "EEExpUtil.h"

#include <maya/MArgList.h>
#include <maya/MFileObject.h>
#include <maya/MItDag.h>
#include <maya/MDagPath.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MObject.h>
#include <maya/MObjectArray.h>
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFnTransform.h>
 
#include <maya/MFloatPointArray.h>
#include <maya/MFloatArray.h>
#include <maya/MPointArray.h>
#include <maya/MStringArray.h>
#include <maya/MFnMesh.h>
#include <maya/MPxFileTranslator.h>

#include "../PRide/engine/error.h"
#include "../PRide/common/format.h"
#include "../PRide/engine/memory.h"
#include "../PRide/math/vector.h"
#include "../PRide/common/string.h"
#include "../PRide/common/string.cpp"
#include "../PRide/common/util.h"
#include "../PRide/common/util.cpp"

MString GetShaderName(MObject shadingEngine)
{
	// attach a function set to the shading engine
	MFnDependencyNode fn( shadingEngine );

	// get access to the surfaceShader attribute. This will be connected to
	// lambert , phong nodes etc.
	MPlug sshader = fn.findPlug("surfaceShader");

	// will hold the connections to the surfaceShader attribute
	MPlugArray materials;

	// get the material connected to the surface shader
	sshader.connectedTo(materials,true,false);

	// if we found a material
	if(materials.length())
	{
		MFnDependencyNode fnMat(materials[0].node());
		return fnMat.name();
	}
	return "none";
}


EEModelExporter::EEModelExporter(void)
{
	m_bShort = false;
}

EEModelExporter::~EEModelExporter(void)
{
}

MStatus	EEModelExporter::reader( const MFileObject& file,
				   const MString& options,
				   FileAccessMode mode)
{
	std::cerr << "[ERROR] This is an exporter plugin only\n" << std::endl;
	return MS::kFailure;
}
extern MStatus buildTriangleList(std::vector<triangle>* triangles, MFnMesh& mesh);

#include <maya/MFnLambertShader.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshFaceVertex.h>
materialData buildMaterial(MObject object)
{
	MFnDependencyNode fn(object);
	MPlug surfaceShader = fn.findPlug("surfaceShader");

	materialData data;
	MPlugArray bb;
	surfaceShader.connectedTo(bb,true,false);

	if(bb.length()>0)
	{
		
		MStatus stat;
		MString COLOR_ATTR("color");
		MString NORMAL_ATTR("normalCamera");
		MString TRANSPARENCY_ATTR("transparency");
		MString TEXTURE_NAME("fileTextureName");
		MString SHADER_ATTR("shader");

		//MString  tmp = bb[0].node().name();
		MPlugArray cc;
		// materialDepNode // this is the main material
		MFnDependencyNode materialDepNode(bb[0].node());
		MString shaderString;
		materialDepNode.findPlug(SHADER_ATTR).getValue(shaderString);

		data.shader = string(shaderString.asChar());


		MPlug decalPlug = materialDepNode.findPlug(COLOR_ATTR, &stat);
		if(stat == MS::kSuccess)
		{	
			decalPlug.connectedTo(cc, true, false);
			if(cc.length() > 0)
			{
				if((cc[0].node()).hasFn(MFn::kFileTexture)) {
					MPlug ftnPlug = MFnDependencyNode(cc[0].node()).findPlug( TEXTURE_NAME, &stat);
					if(stat == MS::kSuccess) {
						MString fileTextureName;
						ftnPlug.getValue(fileTextureName);
						
						string tmp = string(fileTextureName.asChar());
						if(tmp.find(string("/textures")) >= 0)
						{
							unsigned int start = tmp.find(string("/textures")) + 9 + 1;
							data.decalTexture = tmp.substring(start, tmp.length() - start + 1);
						}
						else
						{
							unsigned int start = tmp.findLastOf('//') + 1;
							data.decalTexture = tmp.substring(start, tmp.length() - start + 1);
						}
						
		} } } }

		MPlug normalmapPlug = materialDepNode.findPlug(NORMAL_ATTR, &stat);
		if(stat == MS::kSuccess)
		{	
			normalmapPlug.connectedTo(cc, true, false);
			if(cc.length() > 0)
			{
				if((cc[0].node()).hasFn(MFn::kFileTexture)) {
					MPlug ftnPlug = MFnDependencyNode(cc[0].node()).findPlug( TEXTURE_NAME, &stat);
					if(stat == MS::kSuccess) {
						MString fileTextureName;
						ftnPlug.getValue(fileTextureName);
						
						string tmp = string(fileTextureName.asChar());
						if(tmp.find(string("/textures")) >= 0)
						{
							unsigned int start = tmp.find(string("/textures")) + 9 + 1;
							data.normalTexture = tmp.substring(start, tmp.length() - start + 1);
						}
						else
						{
							unsigned int start = tmp.findLastOf('//') + 1;
							data.normalTexture = tmp.substring(start, tmp.length() - start + 1);
						}
		} } } }

		MPlug auxPlug = materialDepNode.findPlug(TRANSPARENCY_ATTR, &stat);
		if(stat == MS::kSuccess)
		{	
			auxPlug.connectedTo(cc, true, false);
			if(cc.length() > 0)
			{
				if((cc[0].node()).hasFn(MFn::kFileTexture)) {
					MPlug ftnPlug = MFnDependencyNode(cc[0].node()).findPlug( TEXTURE_NAME, &stat);
					if(stat == MS::kSuccess) {
						MString fileTextureName;
						ftnPlug.getValue(fileTextureName);	
						string tmp = string(fileTextureName.asChar());
						if(tmp.find(string("/textures")) >= 0)
						{
							unsigned int start = tmp.find(string("/textures")) + 9 + 1;
							data.auxTexture = tmp.substring(start, tmp.length() - start + 1);
						}
						else
						{
							unsigned int start = tmp.findLastOf('//') + 1;
							data.auxTexture = tmp.substring(start, tmp.length() - start + 1);
						}
		} } } }
	}
	return data;
}

		struct itData
		{
			int vertexCount;
			int *globalVertexIdxs;
			vertex *pVertices;
		};
HRESULT buildMeshTriangleList(MFnMesh& mesh, std::vector<std::vector<triangle>>* triangleSets, std::vector<materialData>* materials)
{
	MIntArray polygons;
	MIntArray trianglesVertexIndices;
	MFloatArray UVa [2] = {MFloatArray(),MFloatArray()};
	MFloatArray UVb [2] = {MFloatArray(),MFloatArray()};
	MFloatArray UVc [2] = {MFloatArray(),MFloatArray()};
	MPointArray positions;
	MFloatVectorArray normals;
	MIntArray vertexCount;
	MIntArray vertexList;
	MStringArray uvSetNames;
	MObjectArray shaderArray;
	MIntArray indiceArray;
	unsigned int meshCount = 0;

	try{

		if(triangleSets == NULL)
			throw EE_EXCEPTION("No triangle list.");

		if(MStatus::kFailure == mesh.getTriangles(polygons,trianglesVertexIndices))
			throw EE_EXCEPTION("getTriangles(...) failed.");

		if(MStatus::kFailure == mesh.getUVSetNames(uvSetNames))
			throw EE_EXCEPTION("getUVSetNames(...) failed.");

		unsigned int uvSetCount = uvSetNames.length();

		if(uvSetCount > 0)
			if(MStatus::kFailure == mesh.getUVs(UVa[0],UVa[1],&uvSetNames[0]))
				throw EE_EXCEPTION("getUVs(...) failed.");
		if(uvSetCount > 1)
			if(MStatus::kFailure == mesh.getUVs(UVb[0],UVb[1],&uvSetNames[1]))
				throw EE_EXCEPTION("getUVs(...) failed.");
		if(uvSetCount > 2)
			if(MStatus::kFailure == mesh.getUVs(UVc[0],UVc[1],&uvSetNames[2]))
				throw EE_EXCEPTION("getUVs(...) failed.");

		if(mesh.getConnectedShaders(0, shaderArray, indiceArray) == MStatus::kFailure)
			throw EE_EXCEPTION("getConnectedShaders(...) failed.");

		if(shaderArray.length() == 0)
			throw EE_EXCEPTION("no shaders assigned or no meshes.");
		meshCount = shaderArray.length();

		if(mesh.getNormals(normals, MSpace::kObject) == MStatus::kFailure)
			throw EE_EXCEPTION("getNormals(...) failed.");

		if(mesh.getPoints(positions, MSpace::kObject) == MStatus::kFailure)
			throw EE_EXCEPTION("getPoints(...) failed.");

		MStatus s;
		MItMeshPolygon iterate(mesh.object(),&s);
		
		std::vector<itData> tempData;

		const int capacity = positions.length();
		vertex* tempArray = new vertex[capacity];


		for(;!iterate.isDone();iterate.next())
		{			
			MItMeshFaceVertex fvIterate(iterate.polygon(),&s);
			int vc = iterate.polygonVertexCount();

			itData data;
			data.pVertices = new vertex[vc];
			data.globalVertexIdxs = new int[vc];
			data.vertexCount = vc;
		
			for(int i =0; i < vc; i++)
			{
				unsigned int vertIdx = iterate.vertexIndex(i);
				unsigned int normIdx = iterate.normalIndex(i);
				int texcIdx; 

				data.globalVertexIdxs[i] = iterate.vertexIndex(i, NULL);

				vertex *v = &data.pVertices[i];

				if(uvSetCount > 0) {
					iterate.getUVIndex(i, texcIdx, &uvSetNames[0]);
					float2 texCoord;
					iterate.getUV(i, texCoord);// vector2(U[texcIdx],V[texcIdx]);
					v->texcoordA.x = UVa[0][texcIdx];//texCoord[0];
					v->texcoordA.y = -UVa[1][texcIdx];//texCoord[1];
				}
				if(uvSetCount > 1) {
					iterate.getUVIndex(i, texcIdx, &uvSetNames[1]);
					float2 texCoord;
					iterate.getUV(i, texCoord);// vector2(U[texcIdx],V[texcIdx]);
					v->texcoordB.x = UVb[0][texcIdx];//texCoord[0];
					v->texcoordB.y = -UVb[1][texcIdx];//texCoord[1];
				}
				if(uvSetCount > 2) {
					iterate.getUVIndex(i, texcIdx, &uvSetNames[2]);
					float2 texCoord;
					iterate.getUV(i, texCoord);// vector2(U[texcIdx],V[texcIdx]);
					v->texcoordC.x = UVc[0][texcIdx];//texCoord[0];
					v->texcoordC.y = -UVc[1][texcIdx];//texCoord[1];
				}

				if(normIdx < normals.length())
					v->normal = vector3(normals[normIdx].x,normals[normIdx].y,normals[normIdx].z);

				if(vertIdx < capacity)
					v->pos = vector3(positions[vertIdx].x,positions[vertIdx].y,positions[vertIdx].z);

				int breakk = 0;
			}

			tempData.push_back(data);
		}
		
		//Iterate through each mesh. Each mesh should consist of its own material.
		for(int materialIndex = 0; materialIndex < meshCount; materialIndex++)
		{	
			materials->push_back(buildMaterial(shaderArray[materialIndex]));

			std::vector<triangle> meshSet;
			for(unsigned int polygonIndex = 0, polygonCount = polygons.length(); polygonIndex < polygonCount; polygonIndex++)
			{
				for(int triangleIndexPolygon = 0, trianglePolyCount = polygons[polygonIndex]; triangleIndexPolygon < trianglePolyCount; triangleIndexPolygon++)
				{
					const int materialID = indiceArray[polygonIndex];

					if(materialIndex == materialID)
					{
						int vertexIndicies[3] = {0};
						vertex A,B,C;

						if(MStatus::kFailure == mesh.getPolygonTriangleVertices(polygonIndex, triangleIndexPolygon,vertexIndicies))
							throw EE_EXCEPTION("getPolygonTriangleVertices(...) failed.");

						itData* polyData = &tempData[polygonIndex];
						/*	
		struct itData
		{
			int vertexCount;
			int *globalVertexIdxs;
			vertex *pVertices;
		};*/
						for(int i = 0; i < polyData->vertexCount; i++)
						{
							int gVI = polyData->globalVertexIdxs[i];
							if(vertexIndicies[0] == gVI)
							{
								A = tempData[polygonIndex].pVertices[i];
							}
							if(vertexIndicies[1] == gVI)
							{
								B = tempData[polygonIndex].pVertices[i];
							}
							if(vertexIndicies[2] == gVI)
							{
								C = tempData[polygonIndex].pVertices[i];
								//C = tempArray[vertexIndicies[2]];
							}
							int breakk = 9;
						}

						/*A = tempArray[vertexIndicies[0]];
						B = tempArray[vertexIndicies[1]];
						C = tempArray[vertexIndicies[2]];*/

						meshSet.push_back(triangle(A,B,C));
					}
				}
			}

			if(meshSet.size() > 0)
			triangleSets->push_back(meshSet);
		}

		for(int n =0;n< tempData.size();n++)
		{
			itData* p = &tempData[n];
			if(p->pVertices)
				delete [] p->pVertices;
			if(p->globalVertexIdxs)
				delete [] p->globalVertexIdxs;
		}

		if(tempArray)
			delete [] tempArray;
		return S_OK;

	}catch(CError::CErrorData error)
	{
		if(triangleSets)
			triangleSets->clear();

		std::cerr << "Error: " << error.mpMessage << " File: " << error.mpFile << " Line: " << error.mLine << "\n";
		return E_FAIL;
	}
}

HRESULT buildPhysicsTriangleList(MFnMesh& mesh, std::vector<vector3>* verticies)
{
	MIntArray polygons;
	MIntArray trianglesVertexIndices;
	MFloatPointArray positions;

	try{

		if(verticies == NULL)
			throw EE_EXCEPTION("No verticies list.");

		if(MStatus::kFailure == mesh.getTriangles(polygons,trianglesVertexIndices))
			throw EE_EXCEPTION("getTriangles(...) failed.");

		if(mesh.getPoints(positions, MSpace::kObject) == MStatus::kFailure)
			throw EE_EXCEPTION("getPoints(...) failed.");
		int numpoints = positions.length();
		for(unsigned int polygonIndex = 0, polygonCount = polygons.length(); polygonIndex < polygonCount; polygonIndex++)
		{
			for(int triangleIndexPolygon = 0, trianglePolyCount = polygons[polygonIndex]; triangleIndexPolygon < trianglePolyCount; triangleIndexPolygon++)
			{
				int vertexIndicies[3] = {0};

				if(MStatus::kFailure == mesh.getPolygonTriangleVertices(polygonIndex, triangleIndexPolygon,vertexIndicies))
					throw EE_EXCEPTION("getPolygonTriangleVertices(...) failed.");

				verticies->push_back(vector3(positions[vertexIndicies[0]]));
				verticies->push_back(vector3(positions[vertexIndicies[1]]));
				verticies->push_back(vector3(positions[vertexIndicies[2]]));
			}
		}
		return S_OK;

	}catch(CError::CErrorData error)
	{
		if(verticies)
			verticies->clear();

		std::cerr << "Error: " << error.mpMessage << " File: " << error.mpFile << " Line: " << error.mLine << "\n";
		return E_FAIL;
	}
}

void GetSelected(MObjectArray& objects,MFn::Type type) {

	 // get the current selection list from maya
	 MSelectionList selected;
	 MGlobal::getActiveSelectionList(selected);

	 // iterate through all selected items
	 for( int i=0; i<selected.length(); ++i )
	 {
		MObject obj;
		
		// returns the i'th selected dependency node
		selected.getDependNode(i,obj);
		MFnDagNode dagNode(obj);

		// if the selected object is of the type we are looking for
		if( obj.hasFn(type) ) {

			for(int j = 0; j < dagNode.parentCount(); j++)
			{
				MObject objParent = dagNode.parent(j);
				if(objParent.hasFn(MFn::kTransform))
				{
					objects.append(obj);
					break;
				}
			}
		}
		else
		// if the selected object is a transform, check it's kids
		if( obj.hasFn(MFn::kTransform) ) 
		{
			MFnTransform fn(obj);
			for(int j=0;j< fn.childCount();++j)
			{
				MObject child = fn.child(j);
				if( child.hasFn(type) )
				{
					objects.append(obj);
					break;
				}
			}
		}
	}
 }


MStatus EEModelExporter::writeData(FILE* pFile)
{	
	MStatus stat;

	modelHeader fileHeader;
	physicsHeader physicsData;

	std::vector<std::vector<triangle>> triangles;
	std::vector<vector3> vertices;
	std::vector<materialData> materials;

	MObjectArray selectedObjects;
 
	try{
		MFnMesh mesh;
		MFnMesh physics;
		bool hasMesh = false;
		bool hasPhysics = false;
		
		MObjectArray exportObjects(0);
		
		GetSelected(exportObjects,MFn::kMesh);

		if(exportObjects.length() == 0)
			return MStatus::kFailure;

		for(int i = 0; i < exportObjects.length(); i++)
		{
			if(stat == MStatus::kSuccess)
			{
				if(!hasPhysics)
				{
					MFnDagNode(exportObjects[i]).attribute("Physics", &stat);
					if(stat == MStatus::kSuccess)
					{
						for(int j = 0; j < MFnDagNode(exportObjects[i]).childCount(); i++)
						{
							MObject child = MFnDagNode(exportObjects[i]).child(j);
							hasPhysics = true;
							if(FAILED(buildPhysicsTriangleList(MFnMesh(child),&vertices)))
								throw EE_EXCEPTION("Failed to build triangle list.");
							break;
						}
					}
				}
				if(!hasMesh)
				{
					MFnDagNode(exportObjects[i]).attribute("Geometry", &stat);
					if(stat == MStatus::kSuccess)
					{
						for(int j = 0; j < MFnDagNode(exportObjects[i]).childCount(); i++)
						{
							MObject child = MFnDagNode(exportObjects[i]).child(j);
							hasMesh = true;
							if(FAILED(buildMeshTriangleList(MFnMesh(child),&triangles,&materials)))
								throw EE_EXCEPTION("Failed to build triangle list.");
							break;
						}
					}
				}
			}
			if(hasMesh && hasPhysics)
				break;
		}
		
		fileHeader.version		= EE_MODEL_ID;
		fileHeader.frameCount	= 1;
		fileHeader.meshCount	= triangles.size();
		
		physicsData.checkBytes	= EE_MODEL_CHECK;
		physicsData.friction	= 1.0f;
		physicsData.vertexCount	= vertices.size();

		//Write the model header
		fwrite(&fileHeader, sizeof(modelHeader), 1, pFile);

		//Write the physics header
		fwrite(&physicsData, sizeof(physicsHeader), 1, pFile);

		for(int i = 0, j = vertices.size(); i < j; i++)
		{
			VECTOR3F v(vertices[i].x,vertices[i].y,vertices[i].z);
			fwrite(&v, sizeof(VECTOR3F), 1, pFile);
		}
		for(int set = 0, j = triangles.size(); set < j; set++)
		{
			meshHeader header;
			EE_ZEROMEMORY(&header, sizeof(header));
			header.vertexCount = triangles[set].size() * 3;
			header.checkBytes = EE_MODEL_CHECK;

			materialData* pMaterial = &materials[set];

			memcpy(header.shaderFilename, pMaterial->shader.c_str(), sizeof(char) * 64);
			memcpy(header.decalFilename, pMaterial->decalTexture.c_str(), sizeof(char) * 64);
			memcpy(header.normalFilename, pMaterial->normalTexture.c_str(), sizeof(char) * 64);
			memcpy(header.auxFilename, pMaterial->auxTexture.c_str(), sizeof(char) * 64);
			fwrite(&header, sizeof(meshHeader),1,pFile);

			for(int i = 0; i < triangles[set].size(); i ++)
			{
				triangles[set][i].write(pFile);
			}
		}

		int endCheck = EE_MODEL_CHECK;
		fwrite(&endCheck, sizeof(int), 1, pFile);

		materials.clear();
		triangles.clear();
		return MStatus::kSuccess;
	
	}catch(CError::CErrorData error)
	{
		return MStatus::kFailure;
	}
}

MStatus	EEModelExporter::writer( const MFileObject& file,
				   const MString& options,
				   FileAccessMode mode )
{
	MStatus     	stat;
	const MString	namesonlyFlag	("namesonly");
	m_bShort		= true;

	//	process the option string passed in from the option script
	if (options.length() > 0) 
	{
        int i, length;
        // Start parsing.
        MStringArray optionList;
        MStringArray theOption;

        options.split(';', optionList);

		//	process each option in the string
		//
		length = optionList.length();
		for( i = 0; i < length; ++i )
		{
            theOption.clear();

			//	split the option using the '=' character. This should make 
			//	theOption[0] hold the option name, and theOption[1] hold the
			//	value of the current option
			//
            optionList[i].split( '=', theOption );

            if( theOption[0] == namesonlyFlag && theOption.length() > 1 ) 
			{
                if(theOption[1].asInt()>0) 
					m_bShort = true;
				else 
					m_bShort = false;
            }
        }
	}
	
	if( mode == MPxFileTranslator::kExportActiveAccessMode )
	{
		std::cerr << "As yet this does not support export selected\nExporting all instead\n";
	}

	FILE* pFile = NULL;

	const char* filename = file.fullName().asChar();
	if(fopen_s(&pFile, filename, WRITE_ASCII ? "w+" : "wb+") == -1)
		std::cerr << "[ERROR] Could not open file";

	if(!pFile) 
	{
		std::cerr << "[ERROR] Could not open file for writing " << file.fullName().asChar() << std::endl;
		return MS::kFailure;
	}
	else 
	{
		if(writeData(pFile) == MS::kFailure)
			std::cerr << "[ERROR] Could not write file " << file.fullName().asChar() << std::endl;

		if(fclose(pFile) == -1)
			std::cerr << "[ERROR] Could not close file";
		pFile = NULL;
	}

	return MS::kSuccess;
}

MString EEModelExporter::defaultExtension () const {
	return g_worldFileFormat;
}

MPxFileTranslator::MFileKind EEModelExporter::identifyFile(	const MFileObject& fileName,
						 const char* buffer,
						 short size) const {
	const char* str = fileName.name().asChar();
	unsigned int len = fileName.name().length();

	return (str[len-3] == g_modelFileFormat[0] && 
			str[len-2] == g_modelFileFormat[1] && 
			str[len-1] == g_modelFileFormat[2]) ? 
	kCouldBeMyFileType :
	kNotMyFileType;
}